*+--------------------------------------------------------------------
*+
*+
*+
*+    Source Module => /home/paul/click/declbust.prg
*+
*+
*+
*+    Click! is a Clipper/Xbase++ source code reformatter.
*+
*+    
*+
*+    Note:Phil is no longer actively developing Click!.
*+
*+    Copyright(C) 1996-1999 by Phil Barnett.
*+
*+       
*+
*+    This program is free software; you can redistribute it and/or modify it
*+
*+    under the terms of the GNU General Public License as published by the
*+
*+    Free Software Foundation; either version 2 of the License, or (at your
*+
*+    option) any later version.
*+
*+    
*+
*+    This program is distributed in the hope that it will be useful, but
*+
*+    WITHOUT ANY WARRANTY; without even the implied warranty of
*+
*+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*+
*+    General Public License for more details.
*+
*+    
*+
*+    You should have received a copy of the GNU General Public License along
*+
*+    with this program; if not, contact the Free Software Foundation, Inc.,
*+
*+    51 Franklin St, Fifth Floor Boston, MA 02110, USA, or www.fsf.org.
*+
*+    
*+
*+    You can contact me at:
*+
*+    
*+
*+    Phil Barnett
*+
*+    Box 944
*+
*+    Plymouth, Florida  32768
*+
*+    
*+
*+    or
*+
*+    
*+
*+    philb@iag.net
*+
*+    
*+
*+    
*+
*+    Ported to Linux and xHarbour compiler, minor feature additions.
*+
*+    Changed version to 3.00xHarbour  Changes
*+
*+    GPL version 3 Copyright (C) 2006-9 by Paul States 
*+
*+    Contact:broadcastmail AT NOSPAM 123mail.org.
*+
*+
*+
*+    Functions: Procedure declbust()
*+               Static Function stripcomnt()
*+               Function tab2space()
*+               Function nextcomma()
*+
*+    Reformatted by Click! 3.00Harbour on Apr-13-2009 at  2:30 pm
*+
*+
*+--------------------------------------------------------------------
*+


#include "directry.ch"
#include "common.ch"
#include "inkey.ch"

#define drivesep "/"  //separated this for future windows version
#define dirsep "/"
#define CVERSION '2.03H'
#define newline  11



*+--------------------------------------------------------------------
*+
*+
*+
*+    Procedure declbust()
*+
*+    Called from ( click.prg    )   1 - procedure click()
*+
*+       Calls    ( obufread.prg )   1 - function bclose()
*+                ( obufread.prg )   2 - function beof()
*+                ( obufread.prg )   1 - function binit()
*+                ( obufread.prg )   4 - function blinenumber()
*+                ( obufread.prg )   1 - function bopen()
*+                ( obufwrit.prg )   1 - procedure bo_close()
*+                ( obufwrit.prg )   1 - function bo_init()
*+                ( obufwrit.prg )   1 - function bo_open()
*+                ( obufwrit.prg )  16 - procedure bo_write()
*+                ( obufread.prg )   4 - function breadline()
*+                ( obufread.prg )   1 - function brelative()
*+                ( click.prg    )   3 - function lineparse()
*+                ( declbust.prg )   2 - function nextcomma()
*+                ( profile.prg  )   4 - function profilestring()
*+                ( click.prg    )   1 - procedure setlinecont()
*+                ( click.prg    )   1 - procedure show_out()
*+                ( declbust.prg )   3 - static function stripcomnt()
*+                ( declbust.prg )   1 - function tab2space()
*+                ( click.prg    )   1 - function tabspstrip()
*+
*+
*+
*+--------------------------------------------------------------------
*+
*+
*+
procedure declbust(cThisFile,cOutFile,cClickini)



local cThisLine
local cThisCommand
local cEndOfLine
local y
local z
local oOutHandle
local aLocals
local nPointer
local cComment
local lStatFunc
local cTempVar
local aPreCombined
local nPreCombined
local oBuffObj3
local nOldPcntDone
local nPcntDone
local nLineLen
local nLocalLen
local lWorkDone    := .f.
local aParts       := {}
local lClickIgnore := .f.,lopen
local lCommentMode := .f.
local aLine        := {}
local aType        := {}
local aParse       := {}
local n
local cStripLine
local cUStripLine
local cOutput
local lConvertDo   := ProfileString(cClickini,'CLICK','CONVERT_OLD_STYLE_FUNCTIONS','NO') == 'YES'
local lConvertInc  := ProfileString(cClickini,'CLICK','CONVERT_TO_INCREMENTOR','YES') == 'YES'
local lCleanInc    := ProfileString(cClickini,'CLICK','CLEAN_UP_INCLUDES','YES') == 'YES'
local lDeflate5    := ProfileString(cClickini,'CLICK','DEFLATE_+','NO') == 'YES'

show_out('Declaration Buster,  Version '+CVERSION)
oBuffObj3 := bInit(cThisFile)
lopen     := Bopen(oBuffObj3)   //call bopen to set the crlf from original file
ferase(cOutFile)
oOutHandle := bo_init(cOutFile,,oBuffObj3[newline],cClickini)
if bo_open(oOutHandle)

   // open the file for buffered reading
   if lopen   //bopen( oBuffObj3 ) had to open early to get eol character(s)

      nPcntDone := 0

      // work on this file until all lines are read
      do while !beof(oBuffObj3)   //enddo around 453

         // get the next line from the buffered read
         cThisLine := bReadLine(oBuffObj3)
         @  6,67 say str(bLineNumber(oBuffObj3),8)         

         nOldPcntDone := nPcntDone
         nPcntDone    := int(bRelative(oBuffObj3) * 50)
         if nOldPcntDone <> nPcntDone
            @  6,14 say replicate('',nPcntDone) color iif(lWorkDone,'R+/B','GR+/B')        
         endif

         if empty(cThisLine)
            bo_write(oOutHandle,oBuffObj3[newline])
            loop
         endif

         cStripLine  := TabSpStrip(cThisLine)
         cUStripLine := upper(cStripLine)

         // pay attention to the 'skip me' flags.
         if cUStripLine == '*+CLICKOFF' .or. cUStripLine == 'TEXT'
            lClickIgnore := .t.
         endif

         if lClickIgnore
            bo_write(oOutHandle,cThisLine+oBuffObj3[newline])
            if cUStripLine == '*+CLICKON' .or. cUStripLine == 'ENDTEXT' .or. ;
                       bEof(oBuffObj3)
               lClickIgnore := .f.
            endif
            loop
         endif

         if left(cUStripLine,1) == '*'
            if lCommentMode .and. at('*/',cThisLine) > 0
               lCommentMode := .f.
            endif
            bo_write(oOutHandle,cThisLine+oBuffObj3[newline])
            loop
         endif

         // write comments directly.
         if left(cStripLine,2) == '//|&&'
            if lCommentMode .and. at('*/',cThisLine) > 0
               lCommentMode := .f.
            endif
            bo_write(oOutHandle,cThisLine+oBuffObj3[newline])
            loop
         endif

         if !lCommentMode
            aParts := lineparse(cStripLine)
            aLine  := aParts[1]
            aType  := aParts[2]
         endif

         // write # directives and loop

         nLineLen := len(aLine)

         if aLine[1] == '#'

            if lCleanInc .and. nLineLen > 4 .and. upper(aLine[3]) == 'INCLUDE'

               aLine[ 3 ] := lower(aLine[3])
               aLine[ 5 ] := strtran(upper(aLine[5]),"'",'"')

            endif

            if aLine[2] == ' '

               adel(aLine,2)
               adel(aType,2)

               asize(aLine,len(aLine) - 1)
               asize(aType,len(aType) - 1)

               cThisLine := ''

               nLineLen := len(aLine)
               for n := 1 to nLineLen
                  cThisLine += aLine[n]
               next

            endif

            bo_write(oOutHandle,ltrim(cThisLine)+oBuffObj3[newline])
            if ascan(aType,'LC') > 0
               do while .t.
                  cThisLine := bReadLine(oBuffObj3)
                  @  6,67 say str(bLineNumber(oBuffObj3),8)         
                  bo_write(oOutHandle,cThisLine+oBuffObj3[newline])
                  aParse := lineparse(cThisLine)
                  aLine  := aParse[1]
                  aType  := aParse[2]
                  if ascan(aType,'LC') == 0
                     exit
                  endif
               enddo
            endif
            loop
         endif

         setlinecont(aType)

         // Check for /* and */ comments and skip them

         if lCommentMode
            if at('*/',cThisLine) > 0
               lCommentMode := .f.
            endif
            bo_write(oOutHandle,cThisLine+oBuffObj3[newline])
            loop
         else
            if atail(aType) == '/*'
               lCommentMode := .t.
            endif
         endif

         // Strip the tabs out of this line.
         cThisLine := ''
         nLineLen  := len(aLine)
         for n := 1 to nLineLen
            if aType[n] == 'S'  // don't strip tabs from inside of strings.
               cThisLine += aLine[n]
            else
               cThisLine += tab2space(aLine[n])
            endif
         next

         aParts := stripcomnt(cThisLine)

         cThisLine := aParts[1]
         cComment  := aParts[2]

         // check to see if the line is a declaration line

         if pad(upper(alltrim(cThisLine)),4) $ ;
                     'LOCA|PRIV|MEMV|STAT|PUBL|FIEL|REPL|METH|CLAS'
         endif

         do case
         case nLineLen > 0 .and. aType[1] $ 'CT' .and. ;
             pad(upper(alltrim(cThisLine)),4) $ ;
             'LOCA|PRIV|MEMV|STAT|PUBL|FIEL|REPL|METH|CLAS'

            // this checks to see if this line has a continuation -> ;
            // and if so it grabs the rest of the pieces and
            // creates a single long line out of them.

            // it also gathers an array of all the lines before they
            // get combined, so if there is nothing to do, we can
            // put them back the way they started.

            cThisline    := alltrim(cThisline)
            aPreCombined := {cThisLine+' '+cComment}
            nPreCombined := 1
            do while right(cThisLine,1) == ';'

               cThisLine := left(cThisLine,len(cThisLine) - 1)
               cTempVar  := bReadLine(oBuffObj3)
               @  6,67 say str(bLineNumber(oBuffObj3),8)         

               aadd(aPreCombined,cTempVar)
               nPreCombined ++

               aParts   := stripcomnt(cTempVar)
               cTempVar := aParts[1]
               cComment += aParts[2]

               cThisLine += alltrim(cTempVar)

            enddo

            // check for STATIC FUNCTION's, yet another overloaded term!
            lStatFunc := .f.
            // find out where second word starts
            nPointer := at(' ',cThisLine)
            if upper(left(cThisline,4)) == 'STAT'
               if upper(left(substr(cThisline,nPointer+1),4)) == 'FUNC'
                  lStatFunc := .t.
               endif
            endif

            // check to see if we can skip this declaration
            // because it only has one part

            if at(',',cThisLine) == 0 .or. ;
                     nextcomma(cThisLine) == 0 .or. ;
                     lStatFunc .or. ;
                     ascan(aLine,{| ww | upper(ww) == 'ALL'}) > 0 .or. ;
                     ascan(aLine,{| ww | upper(ww) == 'NEXT'}) > 0 .or. ;
                     ascan(aLine,{| ww | upper(ww) == 'WHILE'}) > 0 .or. ;
                     ascan(aLine,{| ww | upper(ww) == 'FOR'}) > 0

               // No need to break this one up, so just write it
               // from data gathered before it was assembled from
               // multiple lines.

               for z := 1 to nPreCombined
                  bo_write(oOutHandle,aPreCombined[z]+oBuffObj3[newline])
               next

            else

               // once we get to here, we have found a line which
               // needs to be broken apart.

               // first, we grab the declaration command off of the
               // left end of the line.

               do case
               case upper(left(cThisLine,9)) == 'CLASS VAR'
                  if 'READONLY' $ upper(cThisLine)
                     cThisCommand := 'CLASS VAR READONLY'
                     cThisLine    := substr(cThisLine,11)
                     cThisLine    := stuff(cThisLine,at('READONLY',upper(cThisLine)),8,'')
                  else
                     cThisCommand := left(cThisLine,9)
                     cThisLine    := substr(cThisLine,11)
                  endif

               case upper(left(cThisLine,12)) == 'CLASS METHOD'
                  cThisCommand := left(cThisLine,12)
                  cThisLine    := substr(cThisLine,14)
               otherwise
                  cThisCommand := left(cThisLine,at(' ',cThisLine) - 1)
                  cThisLine    := substr(cThisLine,len(cThisCommand)+1)
               endcase

               // now, we go throught the line and seperate each
               // declaration into the aLocals array
               aLocals := {}

               do while .t.

                  // this gets the next divider comma
                  nPointer := nextcomma(cThisLine)

                  // if nPoiner is still greater than 0, there are still
                  // more parts to seperate

                  if nPointer > 0
                     // grab off this piece and put it on the array
                     aadd(aLocals,left(cThisLine,nPointer - 1))
                     // then trim it off of the working line
                     cThisLine := substr(cThisLine,nPointer+1)
                  else
                     // this is the last one!
                     aadd(aLocals,cThisLine)
                     exit
                  endif

               enddo

               // now, write the output to the file in seperate pieces

               nLocalLen := len(aLocals)
               for y := 1 to nLocalLen
                  bo_write(oOutHandle,cThisCommand+;
                   ' '+;
                   alltrim(aLocals[y])+;
                   cComment+;
                   oBuffObj3[newline])
               next

               lWorkDone := .t.

            endif

         case lConvertInc .and. nLineLen > 8 .and. ;
             upper(aLine[1]) == upper(aLine[5]) .and. ;
             aLine[3] $ ':='
            aParts := lineparse(cThisLine)
            aLine  := aParts[1]
            aType  := aParts[2]

            nLineLen := len(aLine)

            do case
            case ldeflate5  //cannot substitute as lineparser deleted positions
               //just don't convert to incrementors with this option on 6/05 PS
               bo_write(oOutHandle,cThisLine+' '+cComment+oBuffObj3[newline])
            case aLine[9] == '1' .and. nLineLen == 9 .and. aLine[7] $ '+-'
               bo_write(oOutHandle,aLine[1]+aLine[7]+aLine[7]+' '+cComment+oBuffObj3[newline])
               lWorkDone := .t.
            case aLine[7] $ '+-*/^'
               cEndOfLine := ''
               for y := 9 to nLineLen
                  cEndOfLine += aLine[y]
               next
               bo_write(oOutHandle,aLine[1]+' '+aLine[7]+'= '+cEndOfLine+' '+cComment+oBuffObj3[newline])
               lWorkDone := .t.
            otherwise
               bo_write(oOutHandle,cThisLine+' '+cComment+oBuffObj3[newline])
            endcase

         case lConvertDo .and. nLineLen > 2 .and. ;
             upper(aLine[1]) == 'DO' .and. ;
             ! (pad(aLine[3],4) $ 'CASE|WHIL')

            cOutput := aLine[3]+'('

            if nLineLen > 5 .and. upper(aline[5]) == 'WITH'
               for y := 6 to nLineLen
                  if left(aType[y],2) $ '//*'
                     cComment := aLine[y]
                     exit
                  endif
                  if !(left(aLine[y],1) $ ', /()')
                     cOutput += '@'
                  endif
                  cOutput += aLine[y]
               next
            endif

            bo_write(oOutHandle,cOutput+') '+cComment+oBuffObj3[newline])
            lWorkDone := .t.

         otherwise

            // this line is not a declaration line, write it with no change
            bo_write(oOutHandle,cThisLine+cComment+oBuffObj3[newline])

            // and if it is a continuation, write all the lines that follow
            // the continuation
            do while right(alltrim(cThisLine),1) == ';'

               aParts := stripcomnt(bReadLine(oBuffObj3))
               @  6,67 say str(bLineNumber(oBuffObj3),8)         
               cThisLIne := aParts[1]
               cComment  := aParts[2]

               bo_write(oOutHandle,cThisLine+cComment+oBuffObj3[newline])

            enddo

         endcase

      enddo   //matching do while line 109

      // we are done with this file, so close the input and output

      bclose(oBuffObj3)

   endif

endif

bo_close(oOutHandle)

return



*+--------------------------------------------------------------------
*+
*+
*+
*+    Static Function stripcomnt()
*+
*+    Called from ( declbust.prg )   3 - procedure declbust()
*+
*+
*+
*+
*+--------------------------------------------------------------------
*+
*+
*+
static function stripcomnt(cThisLine)



local nPointer  := 0
local nParen    := 0
local nBrack    := 0
local nBrace    := 0
local nSQuote   := 0
local nDQuote   := 0
local inlinecmt := 0
local x
local nNumChars := len(cThisLine)
local cThisChar
local cTwoChars
local cString
local cComment

if '//' $ cThisLine .or. '&&' $ cThisLine

   for x := 1 to nNumChars

      cTwoChars := substr(cThisLine,x,2)

      do case
      case cTwoChars == '/*'
         inlinecmt ++
      case cTwoChars == '*/'
         inlinecmt --
      endcase

      if inlinecmt == 0

         cThisChar := substr(cThisLine,x,1)

         if nSQuote == 0 .and. nDQuote == 0
            do case
            case cThisChar == '('
               nParen ++
            case cThisChar == ')'
               nParen --
            case cThisChar == '{'
               nBrace ++
            case cThisChar == '}'
               nBrace --
            case cThisChar == '['
               nBrack ++
            case cThisChar == ']'
               nBrack --
            endcase
         endif

         do case
         case cThisChar == '"'
            if nSQuote == 0
               if nDQuote == 1
                  nDQuote := 0
               else
                  nDQuote := 1
               endif
            endif
         case cThisChar == "'"
            if nDQuote == 0
               if nSQuote == 1
                  nSQuote := 0
               else
                  nSQuote := 1
               endif
            endif
         endcase

         // if all of this is true, then we just found a comment

         if (cTwoChars == '&&' .or. cTwoChars == '//') .and. ;
                      nParen+nBrack+nBrace+nSQuote+nDQuote == 0

            nPointer := x
            exit

         endif

      endif

   next

endif

if nPointer > 0
   cString  := left(cThisLine,nPointer - 1)
   cComment := substr(cThisLine,nPointer)
else
   cString  := cThisLine
   cComment := ''
endif

return {cString,cComment}



*+--------------------------------------------------------------------
*+
*+
*+
*+    Function tab2space()
*+
*+    Called from ( declbust.prg )   1 - procedure declbust()
*+
*+
*+
*+
*+--------------------------------------------------------------------
*+
*+
*+
function tab2space(aString)



return strtran(aString,chr(K_TAB),' ')



*+--------------------------------------------------------------------
*+
*+
*+
*+    Function nextcomma()
*+
*+    Called from ( declbust.prg )   2 - procedure declbust()
*+
*+
*+
*+
*+--------------------------------------------------------------------
*+
*+
*+
function nextcomma(cThisLine)



/*

Realistically, this is the heartbeat of this entire process. Without this
function, it is impossible to determine where a multiple declaration begins
and ends, since Clipper language allows overloading of symbols in the
language. In this case, the comma is overloaded. It means more than
one thing.

Rules:

Anything inside an inline comment is bypassed

Any comma inside a function should be bypassed

Comma, Parenthesis, Braces and Brackets should be bypassed if they are
inside a string declaration.

Single quotes are bypassed if they are inside
a double quote string declaration

Double quotes are bypassed if they are inside
a single quote string declaration

Note:

This routine does not consider [ ] characters to be string declaration.
Although they can legally be used that way, I have not found it to be
common. It also difficult to determine when they are being used in this
manner.

*/

local nPointer  := 0
local nParen    := 0
local nBrack    := 0
local nBrace    := 0
local nSQuote   := 0
local nDQuote   := 0
local inlinecmt := 0
local x
local nNumChars := len(cThisLine)
local cThisChar
local cTwoChars

if ',' $ cThisLine

   for x := 1 to nNumChars

      cTwoChars := substr(cThisLine,x,2)

      do case
      case cTwoChars == '/*'
         inlinecmt ++
      case cTwoChars == '*/'
         inlinecmt --
      endcase

      if inlinecmt == 0

         cThisChar := substr(cThisLine,x,1)

         if nSQuote == 0 .and. nDQuote == 0
            do case
            case cThisChar == '('
               nParen ++
            case cThisChar == ')'
               nParen --
            case cThisChar == '{'
               nBrace ++
            case cThisChar == '}'
               nBrace --
            case cThisChar == '['
               nBrack ++
            case cThisChar == ']'
               nBrack --
            endcase
         endif

         do case
         case cThisChar == '"'
            if nSQuote == 0
               if nDQuote == 1
                  nDQuote := 0
               else
                  nDQuote := 1
               endif
            endif
         case cThisChar == "'"
            if nDQuote == 0
               if nSQuote == 1
                  nSQuote := 0
               else
                  nSQuote := 1
               endif
            endif
         endcase

         // if all of this is true, then we just found a comma worth dividing on!

         if cThisChar == ',' .and. ;
                    nParen+nBrack+nBrace+nSQuote+nDQuote == 0

            nPointer := x
            exit

         endif

      endif

   next

endif

return nPointer



*+ EOF: declbust.prg
*+
